#include "AudioSubsystem.h"

#include "Audio.h"

#include "Output/Console.h"
#include "File/FileUtils.h"
#include <thread>

#include <string.h> //for memcmp


static RAudioSubsystem* GAudioSubsystem=nullptr;

void RAudioSubsystem::Initialize()
{
    GAudioSubsystem=this;

}

void RAudioSubsystem::Deinitialize()
{
    AudioPlayers.Clear();


    GAudioSubsystem=nullptr;

}

RAudioSubsystemPtr RAudioSubsystem::GetInstance()
{
    return GAudioSubsystem;
}

bool RAudioSubsystem::IsWavFile(const TVector<uint8_t> &InFileData)
{
    if (InFileData.Num() < 12)
    {
        return false;
    }

    if(memcmp(InFileData.Data(), "RIFF", 4) != 0)
    {
        return false;
    }    

    if(memcmp(InFileData.Data() + 8, "WAVE", 4) != 0)
    {
        return false;
    }

    return true;
    
}

bool RAudioSubsystem::IsWavFile(const SPath &InFilePath)
{
    TVector<uint8_t> FileData;
    FileData= SFileUtils::ReadToBinary(InFilePath,12);
    return IsWavFile(FileData);
}

TSharedObjectPtr<RAudio> RAudioSubsystem::LoadWavFile(const TVector<uint8_t> &InFileData)
{    
    //1. check first chunk
    if (InFileData.Num() < 44) //head size is 44 bytes
    {
        WH::PrintLine(U"Wav file size is too small");
        return  nullptr;
    }

    class CWavChunk
    {
    public:
        char ChunkID[4]={0};
        uint32_t ChunkSize=0;
    };

    class CRIFFChunk:public CWavChunk 
    {
    public:
        char Format[4]={0}; //should be "WAVE"
    };

    class CFmtChunk:public CWavChunk
    {
    public:
        uint16_t AudioFormat;
        uint16_t NumChannels;
        uint32_t SampleRate;
        uint32_t ByteRate;
        uint16_t BlockAlign;
        uint16_t BitsPerSample;
    };

    class CDataChunk:public CWavChunk
    {
    public:
        TVector<uint8_t> PCMData;
    };

    class CUnSupportedChunk:public CWavChunk
    {
    public:
        TVector<uint8_t> Data;
    }; 



    //pase first RiffChunk
    CRIFFChunk RiffChunk;
    memcpy(&RiffChunk, InFileData.Data(), sizeof(CRIFFChunk));

    if(memcmp(RiffChunk.ChunkID, "RIFF", 4) != 0)
    {
        WH::PrintLine(U"Wav file format is not correct");
        return nullptr;
    }


    CFmtChunk FmtChunk;
    CDataChunk DataChunk;
    TVector<CUnSupportedChunk> UnSupportedChunks;

    //read next chunk
    uint32_t Offset=sizeof(CRIFFChunk);
    while(Offset<InFileData.Num())
    {
        char ChunkID[4];
        memcpy(ChunkID, InFileData.Data()+Offset, 4);

        if(memcmp(ChunkID, "fmt ", 4) == 0)
        {
            memcpy(&FmtChunk, InFileData.Data()+Offset, sizeof(CFmtChunk));
            Offset+=FmtChunk.ChunkSize+8;
        }
        else if(memcmp(ChunkID, "data", 4) == 0)
        {
            //copy name and size
            memcpy(&DataChunk, InFileData.Data()+Offset, 8);
            DataChunk.PCMData.Resize(DataChunk.ChunkSize);
            //copy data
            memcpy(DataChunk.PCMData.Data(), InFileData.Data()+Offset+8, DataChunk.ChunkSize);
            //DataChunk.PCMData.Reverse();

            Offset+=DataChunk.ChunkSize+8;
        }
        else 
        {
            CUnSupportedChunk& Chunk=UnSupportedChunks.Emplace();
            memcpy(&Chunk, InFileData.Data()+Offset, 8);
            Chunk.Data.Resize(Chunk.ChunkSize);
            memcpy(Chunk.Data.Data(), InFileData.Data()+Offset+8, Chunk.ChunkSize);
            Offset+=Chunk.ChunkSize+8;
        }
    }

    //check if all chunks are read
    if(FmtChunk.ChunkSize==0 || DataChunk.ChunkSize==0)
    {
        WH::PrintLine(U"parse wav file failed");
        return nullptr;
    }

    //3. create audio
    return RAudio::CreateAudio(this, 
        DataChunk.PCMData,
        FmtChunk.NumChannels,
        FmtChunk.SampleRate,
        FmtChunk.BitsPerSample);
}

TSharedObjectPtr<RAudio> RAudioSubsystem::LoadWavFile(const SPath &InFilePath)
{
    TVector<uint8_t> FileData;
    FileData= SFileUtils::ReadToBinary(InFilePath);
    return LoadWavFile(FileData);
}

void RAudioSubsystem::PlaySound(TSharedObjectPtr<RAudio> InAudio)
{
    auto AudioPlayer=
        CreateAudioPlayer(InAudio);

    AudioPlayer->Open();
    AudioPlayer->Play();

    AudioPlayers.Add(AudioPlayer);
}

TSharedObjectPtr<RAudioPlayer> RAudioSubsystem::CreateAudioPlayer(TSharedObjectPtr<RAudio> InAudio)
{
    auto AudioPlayer=NewObject<RAudioPlayer>(this);
    AudioPlayers.Add(AudioPlayer);
    AudioPlayer->SetAudio(InAudio);

    return AudioPlayer;
}

void RAudioSubsystem::Tick(float DeltaSeconds)
{
    for (int i = 0; i < AudioPlayers.Num();)
    {
        if(!AudioPlayers[i]->IsPlaying())
        {
            AudioPlayers.RemoveAt(i);
        }
        else
        {
            i++;
        }
    }

}
